from typing import Generic, Dict, List, Optional
from Constraint import Constraint
from TypeDefinition import V, D


class CSP(Generic[V, D]):
    def __init__(self, variables: List[V], domains: Dict[V, List[D]], problemtype: str, preferences) -> None:
        self.variables = variables
        self.domains = domains
        self.constraints: Dict[V, List[Constraint[V, D]]] = {}
        self.assignment: Dict[V, D] = {}
        self.problemtype: str = problemtype
        self.preferences = preferences
        for variable in self.variables:
            self.constraints[variable] = []
            if variable not in self.domains:
                raise LookupError("Every variable should have a domain assigned to it")

    def add_constraint(self, constraint: Constraint[V, D]) -> None:
        for variable in constraint.variables:
            if variable not in self.variables:
                raise LookupError("Variable in constraint not in CSP")
            else:
                self.constraints[variable].append(constraint)

    def consistent(self, variable: V, assignment: Dict[V, D]) -> bool:
        for constraint in self.constraints[variable]:
            if not constraint.satisfied(assignment):
                return False
        return True

    def backtracking_search(self, assignment: Dict[V, D] = {}) -> Optional[Dict[V, D]]:
        print(assignment)
        if bool(self.assignment):
            if self.preferences.preferences(self.assignment, assignment):
                if len(assignment) == len(self.variables):
                    self.assignment = assignment
                    return assignment
            else:
                return assignment
        else:
            if len(assignment) == len(self.variables):
                self.assignment = assignment
                return assignment

        unassigned: List[V] = [v for v in self.variables if v not in assignment]
        first: V = unassigned[0]
        for value in self.domains[first]:
            local_assignment = assignment.copy()
            local_assignment[first] = value
            if self.consistent(first, local_assignment):
                result: Optional[Dict[V, D]] = self.backtracking_search(local_assignment)
                if self.problemtype == "csp":
                    if result is not None:
                        return result
                elif self.problemtype == "cop":
                    pass
                else:
                    raise LookupError("Input error, Please input the right option of problem error")
        return None
